﻿Imports System.Runtime.CompilerServices
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Drawing

''' <summary>
''' Базовый класс контроллера. Все контроллеры приложения рекомендуется наследовать от этого класса.
''' </summary>
''' <remarks>Класс является основой для построения классов контроллеров в приложении.</remarks>
Public MustInherit Class AzoxController
    Inherits DataBaseController

    ''' <summary>
    ''' Предоставляет обозначения размеров изображения.
    ''' </summary>
    ''' <remarks></remarks>
    Enum ImageSize
        Original
        Large
        Medium
        Small
    End Enum

#Region "Методы"

    ''' <summary>
    ''' Создает объект <see cref="ViewResult" />, который выполняет визуализацию модели <see cref="Page" /> в представление.
    ''' </summary>
    ''' <returns>Результат представления, выполняющий визуализацию представления в ответ.</returns>
    ''' <remarks></remarks>
    Protected Friend Function PageView() As ViewResult
        Return PageView(String.Empty)
    End Function

    ''' <summary>
    ''' Создает объект <see cref="ViewResult" />, который выполняет визуализацию модели <see cref="Page" /> в представление, имя которого передан через параметр <paramref name="viewName" />.
    ''' </summary>
    ''' <param name="viewName">Имя представления.</param>
    ''' <returns>Результат представления, выполняющий визуализацию представления в ответ.</returns>
    ''' <remarks></remarks>
    Protected Friend Function PageView(viewName As String) As ViewResult
        Dim actionName As String = Request.RequestContext.RouteData.Values("action")
        Dim controllerName As String = Request.RequestContext.RouteData.Values("controller")

        Dim page As Page = Db.Pages.SingleOrDefault(Function(m) m.ActionName.Equals(actionName) And m.ControllerName.Equals(controllerName))

        'Если в базе данных отсутствует нужная модель, то она будет создана и сохранена в базе.
        If IsNothing(page) Then
            page = Db.Pages.Add(New Page With {.Id = Guid.NewGuid, .Title = actionName, .ActionName = actionName.ToLower, .ControllerName = controllerName.ToLower})
            Db.SaveChanges()
        End If

        'Выводим в преопределенные динамический словарь данные модели.
        ViewBag.Title = page.Title
        ViewBag.EditUrl = Url.Action("edit", "admin", New With {.id = page.Id, .area = "pages", .returnUrl = Url.Action(actionName, controllerName)})

        Return View(viewName, page)
    End Function

#End Region

#Region "Вспомогательные методы"

    ''' <summary>
    ''' Перенаправляет либо на указанный в параметре <paramref name="returnUrl" /> локальный путь, либо на метод действия <c>Index</c>.
    ''' </summary>
    ''' <param name="returnUrl">Локальный путь возврата.</param>
    ''' <returns>Объект <see cref="ActionResult" />.</returns>
    ''' <remarks></remarks>
    Protected Friend Overridable Function RedirectToLocal(returnUrl As String) As ActionResult
        If Url.IsLocalUrl(returnUrl) Then
            Return Redirect(returnUrl)
        Else
            Return RedirectToAction("index")
        End If
    End Function

    ''' <summary>
    ''' Возвращает в поток из источника данных изображение.
    ''' </summary>
    ''' <param name="id">Уникальный идентификатор изображения.</param>
    ''' <param name="size">Размер изображения.</param>
    ''' <returns>Объект <see cref="ActionResult" />.</returns>
    ''' <remarks></remarks>
    Protected Friend Overridable Function GetImageFile(id As Guid, size As ImageSize) As ActionResult
        Dim imageBytes() As Byte

        Select Case size
            Case ImageSize.Large
                imageBytes = Db.Thumbnails.Where(Function(m) m.Id.Equals(id)).Select(Function(m) m.Large).SingleOrDefault()
            Case ImageSize.Medium
                imageBytes = Db.Thumbnails.Where(Function(m) m.Id.Equals(id)).Select(Function(m) m.Medium).SingleOrDefault()
            Case ImageSize.Small
                imageBytes = Db.Thumbnails.Where(Function(m) m.Id.Equals(id)).Select(Function(m) m.Small).SingleOrDefault()
            Case Else
                imageBytes = Db.Thumbnails.Where(Function(m) m.Id.Equals(id)).Select(Function(m) m.Original).SingleOrDefault()
        End Select

        If IsNothing(imageBytes) Then Return HttpNotFound()

        Dim contentType As String
        Using stream As New MemoryStream(imageBytes)
            Dim imgFormat = Image.FromStream(stream, True, True).RawFormat

            If imgFormat.Equals(ImageFormat.Png) Then
                contentType = "image/png"
            ElseIf imgFormat.Equals(ImageFormat.Gif) Then
                contentType = "image/gif"
            ElseIf imgFormat.Equals(ImageFormat.Bmp) Then
                contentType = "image/bmp"
            Else
                contentType = "image/jpeg"
            End If
        End Using

        Return File(imageBytes, contentType)
    End Function

    ''' <summary>
    ''' Возвращает URL изображения. Если в метод передается пустой <see cref="Guid" />, то возвращается URL замещающего узображения. По умолчанию <c>~/Images/blank.png</c>.
    ''' </summary>
    ''' <param name="imageId">Уникальный идентификатор изображения.</param>
    ''' <param name="size">Размер изображения.</param>
    ''' <returns><see cref="String" />, содержащая URL изображения.</returns>
    ''' <remarks></remarks>
    Protected Friend Overridable Function GetImageUrl(imageId As Guid, size As ImageSize) As String
        If imageId.Equals(Guid.Empty) Then Return Url.Content(My.Settings.BlankThumbnailUrl)

        Dim controllerName As String = "images"

        Dim imageUrl As String = Url.Action("original", controllerName, New With {.id = imageId, .area = String.Empty})
        Select Case size
            Case ImageSize.Large
                imageUrl = Url.Action("large", controllerName, New With {.id = imageId, .area = String.Empty})
            Case ImageSize.Medium
                imageUrl = Url.Action("medium", controllerName, New With {.id = imageId, .area = String.Empty})
            Case ImageSize.Small
                imageUrl = Url.Action("small", controllerName, New With {.id = imageId, .area = String.Empty})
        End Select

        Return imageUrl
    End Function

    ''' <summary>
    ''' Сохраняет в источнике данных загруженное изображение, возвращая уникальный идентификатор.
    ''' </summary>
    ''' <param name="imageFile">Объект <see cref="HttpPostedFileBase" />, содержащий данные загруженного изображения.</param>
    ''' <returns>Структура <see cref="Guid" />, содержащая уникальный идентификатор загруженного изображения.</returns>
    ''' <remarks></remarks>
    Protected Friend Function SaveImage(imageFile As HttpPostedFileBase, largeWidth As Integer, mediumWidth As Integer, smallWidth As Integer) As Guid
        If IsNothing(imageFile) Then Return Guid.Empty
        If Not imageFile.ContentType.Contains("image") Then Throw New ArgumentException("Загружаемый файл должен быть изображением.")

        Dim format As ImageFormat = ImageFormat.Jpeg

        Select Case imageFile.ContentType
            Case "image/png"
                format = ImageFormat.Png
            Case "image/gif"
                format = ImageFormat.Gif
            Case "image/bmp"
                format = ImageFormat.Bmp
        End Select

        Return Db.Thumbnails.Add(FillImage(New Thumbnail With {.Id = Guid.NewGuid}, Image.FromStream(imageFile.InputStream, True, True), format, largeWidth, mediumWidth, smallWidth)).Id
    End Function

    ''' <summary>
    ''' Обновляет в источнике данных изображение на вновь загруженное, возвращая уникальный идентификатор. Если в параметр <paramref name="thumbnailId" /> передан пустой <see cref="Guid" />, то метод создаст в источнике данных новое изображение.
    ''' </summary>
    ''' <param name="imageFile">Объект <see cref="HttpPostedFileBase" />, содержащий данные загруженного изображения.</param>
    ''' <param name="largeWidth">Ширина большого изображения в пикселях.</param>
    ''' <param name="mediumWidth">Ширина среднего изображения в пикселях.</param>
    ''' <param name="smallWidth">Ширина малого изображения в пикселях.</param>
    ''' <param name="imageId">Уникальный идентификатор обновляемого изображения. Если передать пустой <see cref="Guid" />, то будет добавлено новое изображение.</param>
    ''' <returns>Структура <see cref="Guid" />, содержащая уникальный идентификатор загруженного изображения.</returns>
    ''' <remarks></remarks>
    Protected Friend Function UpdateImage(imageFile As HttpPostedFileBase, largeWidth As Integer, mediumWidth As Integer, smallWidth As Integer, imageId As Guid) As Guid
        If IsNothing(imageFile) Then Return imageId
        If Not imageFile.ContentType.Contains("image") Then Throw New ArgumentException("Загружаемый файл должен быть изображением.")

        Dim format As ImageFormat = ImageFormat.Jpeg

        Select Case imageFile.ContentType
            Case "image/png"
                format = ImageFormat.Png
            Case "image/gif"
                format = ImageFormat.Gif
            Case "image/bmp"
                format = ImageFormat.Bmp
        End Select

        If imageId.Equals(Guid.Empty) Then
            Return Db.Thumbnails.Add(FillImage(New Thumbnail With {.Id = Guid.NewGuid}, Image.FromStream(imageFile.InputStream, True, True), format, largeWidth, mediumWidth, smallWidth)).Id
        End If

        Db.Entry(FillImage(New Thumbnail With {.Id = imageId}, Image.FromStream(imageFile.InputStream, True, True), format, largeWidth, mediumWidth, smallWidth)).State = EntityState.Modified
        Return imageId
    End Function

    ''' <summary>
    ''' Удаляет в источнике данных изображение.
    ''' </summary>
    ''' <param name="id">Уникальный идентификатор удаляемого изображения.</param>
    ''' <remarks></remarks>
    Protected Friend Sub DeleteImage(id As Guid)
        Dim image As Thumbnail = Db.Thumbnails.Find(id)
        If Not IsNothing(image) Then
            Db.Thumbnails.Remove(image)
        End If
    End Sub

    Private Function FillImage(currentImage As Thumbnail, newImage As Image, format As ImageFormat, largeWidth As Integer, mediumWidth As Integer, smallWidth As Integer) As Thumbnail
        Dim originalWidth As Integer = newImage.Width
        Dim originalHeight As Integer = newImage.Height

        Dim largeHeight As Integer = originalHeight / (originalWidth / largeWidth)
        Dim mediumHeight As Integer = originalHeight / (originalWidth / mediumWidth)
        Dim smallHeight As Integer = originalHeight / (originalWidth / smallWidth)

        'Оригинальный размер.
        Using stream As New MemoryStream
            newImage.Save(stream, format)
            currentImage.Original = stream.ToArray()
        End Using

        'Большое изображение.
        Using stream As New MemoryStream
            Using b As New Bitmap(newImage, largeWidth, largeHeight)
                Using encoderParams As New EncoderParameters(1)
                    encoderParams.Param(0) = New EncoderParameter(Encoder.Quality, 95)
                    b.Save(stream, GetEncoder(format), encoderParams)
                End Using
            End Using
            currentImage.Large = stream.ToArray()
        End Using

        'Среднее изображение.
        Using stream As New MemoryStream
            Using b As New Bitmap(newImage, mediumWidth, mediumHeight)
                Using encoderParams As New EncoderParameters(1)
                    encoderParams.Param(0) = New EncoderParameter(Encoder.Quality, 95)
                    b.Save(stream, GetEncoder(format), encoderParams)
                End Using
            End Using
            currentImage.Medium = stream.ToArray()
        End Using

        'Малое изображение.
        Using stream As New MemoryStream
            Using b As New Bitmap(newImage, smallWidth, smallHeight)
                Using encoderParams As New EncoderParameters(1)
                    encoderParams.Param(0) = New EncoderParameter(Encoder.Quality, 95)
                    b.Save(stream, GetEncoder(format), encoderParams)
                End Using
            End Using
            currentImage.Small = stream.ToArray()
        End Using

        Return currentImage
    End Function

    Private Function GetEncoder(format As ImageFormat) As ImageCodecInfo
        Dim codecs As ImageCodecInfo() = ImageCodecInfo.GetImageDecoders()

        Dim codec As ImageCodecInfo
        For Each codec In codecs
            If codec.FormatID = format.Guid Then
                Return codec
            End If
        Next codec
        Return Nothing
    End Function

#End Region
End Class
